﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Markup;
using System.ComponentModel;
using System.Windows.Media.Effects;
using System.Collections.ObjectModel;
using System.IO;
using System.Diagnostics;
using Microsoft.Win32;
using System.Windows.Media.Animation;
using System.Reflection;

namespace ShaderPad
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        private UIOptions options = new UIOptions();
        private ObservableCollection<InputValue> shaderInputs = new ObservableCollection<InputValue>();
        private ObservableCollection<InputValue> textureInputs = new ObservableCollection<InputValue>();
        CustomShaderEffect se=new CustomShaderEffect( new PixelShader() { UriSource = new Uri(@"pack://application:,,,/ShaderPad;component/Effects/PlainEffect.ps") });
        CustomShaderEffect grayScaleEffect = new CustomShaderEffect(new PixelShader() { UriSource = new Uri(@"pack://application:,,,/ShaderPad;component/Effects/GrayScaleEffect.ps") });

        public Window1()
        {
            this.Loaded += new RoutedEventHandler(Window1_Loaded);
            InitializeComponent();

            elementTree.ElementSelectionChanged+=this.elementTree_ElementSelectionChanged;

            this.DataContext = options;



            for (int i = 0; i < 32; ++i)
            {
                if (i < 3)
                {
                    textureInputs.Add(
                        new InputValue("Tex" + (i + 1).ToString())
                        {
                            Texture = new ImageBrush()
                        });
                }

                InputValue iv = new InputValue("Val" + i.ToString());
                this.RegisterName(iv.Name, iv);
                shaderInputs.Add(iv);
            }
            lvShaderTextures.ItemsSource = textureInputs;
            lvShaderInputs.ItemsSource = shaderInputs;

            txtXAML.Text = @"<Grid 
    xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" 
    xmlns:sys=""clr-namespace:System;assembly=mscorlib"" 
    xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""> 
    <StackPanel Width=""200"">
        <Button Height=""20"" Width=""150"" >Test</Button> 
        <ListBox >
            <Button>b1</Button>
            <ComboBox Width=""190"">
                <sys:String>option1</sys:String>
                <sys:String>option2</sys:String>
                <sys:String>option3</sys:String>
            </ComboBox>
            <CheckBox>Checkbox Iem</CheckBox>
            <ToggleButton>tb2</ToggleButton>
            <Button>b3</Button>            
        </ListBox>
    </StackPanel>
    <Viewbox Margin=""30,60,0,0"" Width=""300"" Height=""300"">
        <Border BorderThickness=""25"" BorderBrush=""White"" Margin=""5"">
            <Border.RenderTransform>
                <RotateTransform Angle=""10""/>
            </Border.RenderTransform>
            <Image Source=""C:\Users\Public\Pictures\Sample Pictures\Forest.jpg""/> 
        </Border>
    </Viewbox>
</Grid>";
            txtXAML_TextChanged(this, null);

        }

        void Window1_Loaded(object sender, RoutedEventArgs e)
        {
            this.Title += " - v" + Assembly.GetExecutingAssembly().GetName().Version.ToString();
        }

        private void txtXAML_TextChanged(object sender, TextChangedEventArgs e)
        {
            System.IO.MemoryStream ms = new System.IO.MemoryStream();
            ms.Write(ASCIIEncoding.ASCII.GetBytes(txtXAML.Text.ToCharArray()), 0, txtXAML.Text.Length);
            ms.Position = 0;
            try
            {
                preView.Child = (UIElement)XamlReader.Load(ms);
                elementTree.ElementSource = preView;
                txtXAML.Foreground = Brushes.Black;
                Canvas.SetLeft(preView, 0);
                Canvas.SetTop(preView, 0);
            }
            catch (Exception ex)
            {
                Grid g = new Grid();
                TextBlock errmsg = new TextBlock();
                errmsg.Text = ex.ToString();
                g.Children.Add(errmsg);
                preView.Child = g;
                txtXAML.Foreground = Brushes.Red;
            }
        }

        private void btnRefresh_Click(object sender, RoutedEventArgs e)
        {
            txtXAML_TextChanged(this, null);
        }

        private void autoParse_Click(object sender, RoutedEventArgs e)
        {
            if (autoParse.IsChecked.Value == true)
            {
                txtXAML.TextChanged += txtXAML_TextChanged;
            }
            else
            {
                txtXAML.TextChanged -= txtXAML_TextChanged;
            }
        }

        private void btnCompile_Click(object sender, RoutedEventArgs e)
        {
            txtOutput.Text = "";


            string temporaryShaderSourceFilePath = string.Format("{0}\\ShaderEffect", Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));

            using (FileStream fs = new FileStream(temporaryShaderSourceFilePath + ".fx", FileMode.Create))
            {
                byte[] data = Encoding.ASCII.GetBytes(txtPixelShader.Text);
                fs.Write(data, 0, data.Length);
            }


            ProcessStartInfo psi = new ProcessStartInfo(options.FXCPath);
            psi.CreateNoWindow = true;
            psi.UseShellExecute = false;

            psi.RedirectStandardError = true;

            psi.Arguments = string.Format(options.FXCOptions + " /Fo\"{0}.ps\" \"{0}.fx\"", temporaryShaderSourceFilePath);
            using (Process p = Process.Start(psi))
            {

                StreamReader sr = p.StandardError;
                txtOutput.Text = sr.ReadToEnd().Replace(temporaryShaderSourceFilePath + ".fx", "Line ");

                p.WaitForExit();
            }

            if (txtOutput.Text == string.Empty)
            {
                output.Height = 20;
                txtOutput.Foreground = Brushes.Green;
                txtOutput.Text = "Success";

                DependencyProperty[] inputs = new DependencyProperty[]{
                    CustomShaderEffect.Val0Property,CustomShaderEffect.Val1Property,CustomShaderEffect.Val2Property,CustomShaderEffect.Val3Property,
                    CustomShaderEffect.Val7Property,CustomShaderEffect.Val6Property,CustomShaderEffect.Val5Property,CustomShaderEffect.Val4Property,
                    CustomShaderEffect.Val8Property,CustomShaderEffect.Val9Property,CustomShaderEffect.Val10Property,
                    CustomShaderEffect.Val11Property,CustomShaderEffect.Val12Property,CustomShaderEffect.Val13Property,
                    CustomShaderEffect.Val17Property,CustomShaderEffect.Val16Property,CustomShaderEffect.Val15Property,CustomShaderEffect.Val14Property,
                    CustomShaderEffect.Val18Property,CustomShaderEffect.Val19Property,CustomShaderEffect.Val20Property
                    ,CustomShaderEffect.Val21Property,CustomShaderEffect.Val22Property,CustomShaderEffect.Val23Property,
                    CustomShaderEffect.Val27Property,CustomShaderEffect.Val26Property,CustomShaderEffect.Val25Property,CustomShaderEffect.Val24Property,
                    CustomShaderEffect.Val28Property,CustomShaderEffect.Val29Property,CustomShaderEffect.Val30Property,CustomShaderEffect.Val31Property};


                DependencyProperty[] samplers = new DependencyProperty[]{
                    CustomShaderEffect.Tex1Property,CustomShaderEffect.Tex2Property,CustomShaderEffect.Tex3Property};
                PixelShader ps = new PixelShader();
                ps.UriSource = new Uri(temporaryShaderSourceFilePath + ".ps");
                se = new CustomShaderEffect(ps);
                for (int i = 0; i < 32; ++i)
                {
                    Binding b = new Binding();
                    b.Source = shaderInputs[i];
                    b.Path = new PropertyPath("Value");
                    b.Mode = BindingMode.TwoWay;
                    BindingOperations.SetBinding(se, inputs[i], b);

                    if (i < 3)
                    {
                        b = new Binding();
                        b.Source = textureInputs[i];
                        b.Path = new PropertyPath("Texture");
                        BindingOperations.SetBinding(se, samplers[i], b);
                    }
                }

                if (elementTree.SelectedItem != null)
                {
                    UIElement uie = elementTree.SelectedItem as UIElement;
                    uie.Effect = se;
                }
                //preView.Effect= se;
            }
            else
            {
                output.Height = 60;
                txtOutput.Foreground = Brushes.Red;
            }
        }

        private void btnLoadShader_Click(object sender, RoutedEventArgs e)
        {
            this.Effect = grayScaleEffect;
            try
            {
                OpenFileDialog ofd = new OpenFileDialog();
                ofd.Filter = "HLSL Shader Files|*.fx|All Files|*.*";
                if (ofd.ShowDialog(this).Value == true)
                {
                    string shadercode = File.ReadAllText(ofd.FileName);
                    txtPixelShader.Text = shadercode;
                    txtEfectName.Text = System.IO.Path.GetFileName(ofd.FileName);
                }
            }
            finally
            {
                this.Effect = null;
            }
        }

        private void btnSaveShader_Click(object sender, RoutedEventArgs e)
        {
            this.Effect = grayScaleEffect;
            try
            {
                SaveFileDialog sfd = new SaveFileDialog();
                sfd.OverwritePrompt = true;
                sfd.Filter = "HLSL Shader Files|*.fx|All Files|*.*";
                sfd.FileName = txtEfectName.Text;
                if (sfd.ShowDialog(this).Value == true)
                {
                    using (FileStream fs = new FileStream(sfd.FileName, FileMode.Create))
                    {
                        byte[] data = Encoding.ASCII.GetBytes(txtPixelShader.Text);
                        fs.Write(data, 0, data.Length);
                    }
                    txtEfectName.Text = System.IO.Path.GetFileName(sfd.FileName);
                }
            }
            finally
            {
                this.Effect = null;
            }
        }

        private bool isDragging = false;
        private Point dragStart;
        private double preViewLeft, preViewTop;
        private void preView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            isDragging = true;
            dragStart = e.GetPosition(canvas);
            preViewLeft = Canvas.GetLeft(preView);
            preViewTop = Canvas.GetTop(preView);
        }

        private void preView_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            isDragging = false;
            if (Mouse.Captured == preView)
            {
                Mouse.Capture(null);
            }
        }

        private void preView_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (isDragging)
            {
                Point cpos = e.GetPosition(canvas);
                if (Math.Abs(cpos.X - dragStart.X) > 2.0 ||
                    Math.Abs(cpos.Y - dragStart.Y) > 2.0)
                {
                    Canvas.SetLeft(preView, preViewLeft + cpos.X - dragStart.X);
                    Canvas.SetTop(preView, preViewTop + cpos.Y - dragStart.Y);
                    Mouse.Capture(preView);
                }
            }
        }

        private void ChangeTexture_Click(object sender, RoutedEventArgs e)
        {
            Button cbutton = sender as Button;
            InputValue iv = cbutton.Tag as InputValue;
            if (cbutton == null || iv == null) return;

            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "Image Files|*.bmp;*.jpg;*.png|All Files|*.*";
            if (ofd.ShowDialog(this).Value == true)
            {
                iv.Texture = new ImageBrush(new BitmapImage(new Uri(ofd.FileName, UriKind.Absolute)));
            }
        }

        private void CheckBox_Click(object sender, RoutedEventArgs e)
        {
            CheckBox cbox = sender as CheckBox;

            if (cbox == null) return;

            InputValue iv = cbox.Tag as InputValue;

            if (iv == null) return;

            if (iv.storyboard != null)
            {
                iv.storyboard.Stop(this);
            }

            if (cbox.IsChecked.Value == true)
            {
                if (iv.storyboard == null)
                {
                    Storyboard sb = new Storyboard();
                    DoubleAnimation valueAnimation = iv.animation;

                    valueAnimation.From = iv.MinValue;
                    valueAnimation.To = iv.MaxValue;

                    valueAnimation.Duration = new Duration(TimeSpan.FromSeconds(5));
                    //val0animation.RepeatBehavior = RepeatBehavior.Forever;

                    iv.storyboard = sb;
                    Storyboard.SetTargetName(valueAnimation, iv.Name);
                    Storyboard.SetTargetProperty(valueAnimation, new PropertyPath(InputValue.ValueProperty));

                    sb.Children.Add(valueAnimation);

                    sb.Completed += (EventHandler)delegate(object o, EventArgs ee)
                    {
                        cbox.IsChecked = false;
                    };

                    sb.Begin(this, true);
                }
                else
                {
                    DoubleAnimation val0animation = iv.storyboard.Children[0] as DoubleAnimation;

                    val0animation.From = iv.MinValue;
                    val0animation.To = iv.MaxValue;

                    iv.storyboard.Begin(this, true);
                }
            }
        }

        private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {

        }

        private void btnOptions_Click(object sender, RoutedEventArgs e)
        {
            this.Effect = grayScaleEffect;
            try
            {
                UIOptions ops = new UIOptions();
                ops.FXCPath = options.FXCPath;
                ops.FXCOptions = options.FXCOptions;

                OptionsWindow optionsWindow = new OptionsWindow();
                optionsWindow.DataContext = ops;
                if (optionsWindow.ShowDialog().Value == true)
                {
                    options.FXCPath = ops.FXCPath;
                    options.FXCOptions = ops.FXCOptions;
                }
            }
            finally
            {
                this.Effect = null;
            }
        }

        private void chkAutoReverse_Click(object sender, RoutedEventArgs e)
        {
            CheckBox cbox = sender as CheckBox;

            if (cbox == null) return;

            InputValue iv = cbox.Tag as InputValue;

            if (iv == null) return;

            iv.animation.AutoReverse = cbox.IsChecked.Value;
        }

        private void chkReverse_Click(object sender, RoutedEventArgs e)
        {

        }

        private void btnLoadShaderFromWeb_Click(object sender, RoutedEventArgs e)
        {

        }

        private void btnSaveShaderToWeb_Click(object sender, RoutedEventArgs e)
        {

        }

        private void elementTree_ElementSelectionChanged(object sender, RoutedPropertyChangedEventArgs<LogicalTreeItem> e)
        {
            if (e.OldValue != null)
            {
                UIElement uie = e.OldValue.Element as UIElement;
                uie.Effect = null;
            }
            if (e.NewValue != null)
            {
                UIElement uie = e.NewValue.Element as UIElement;
                uie.Effect = se;
            }
        }

    }

    internal class UIOptions : INotifyPropertyChanged
    {
        private double zoomFactor = 1.0;
        public double ZoomFactor
        {
            get
            {
                return zoomFactor;
            }
            set
            {
                if (zoomFactor != value)
                {
                    zoomFactor = value;
                    RaisePropertyChanged("ZoomFactor");
                }
            }
        }

        private string fxcpath = @"C:\Program Files\Microsoft DirectX SDK (June 2008)\Utilities\bin\x86\fxc";
        public string FXCPath
        {
            get
            {
                return fxcpath;
            }
            set
            {
                if (fxcpath != value)
                {
                    fxcpath = value;
                    RaisePropertyChanged("FXCPath");
                }
            }
        }


        private string fxcoptions = @"/T ps_2_0 /E main";
        public string FXCOptions
        {
            get
            {
                return fxcoptions;
            }
            set
            {
                if (fxcoptions != value)
                {
                    fxcoptions = value;
                    RaisePropertyChanged("FXCOptions");
                }
            }
        }


        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }

    internal class InputValue : DependencyObject
    {
        public DoubleAnimation animation = new DoubleAnimation();
        public Storyboard storyboard = null;

        public InputValue(string n)
        {
            Name = n;
        }



        public string Name
        {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Name.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof(string), typeof(InputValue), new UIPropertyMetadata(""));




        public Brush Texture
        {
            get { return (Brush)GetValue(TextureProperty); }
            set { SetValue(TextureProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Texture.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TextureProperty =
            DependencyProperty.Register("Texture", typeof(Brush), typeof(InputValue), new UIPropertyMetadata(Brushes.Black));




        public double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(double), typeof(InputValue), new UIPropertyMetadata(0.0));




        public double MinValue
        {
            get { return (double)GetValue(MinValueProperty); }
            set { SetValue(MinValueProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MinValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinValueProperty =
            DependencyProperty.Register("MinValue", typeof(double), typeof(InputValue), new UIPropertyMetadata(-1.0));



        public double MaxValue
        {
            get { return (double)GetValue(MaxValueProperty); }
            set { SetValue(MaxValueProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MaxValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MaxValueProperty =
            DependencyProperty.Register("MaxValue", typeof(double), typeof(InputValue), new UIPropertyMetadata(1.0));



    }
}
